<?php
/**
*   Abstract class which facilitates PHP's `OOP' usage
*   Defines all PHP magic methods except clone,construct,destruct,invoke
*
*/

abstract class Yz_Class
    implements ArrayAccess,Iterator,Countable
{

    /** Python-style 'dictionary' for the class instance's values
     *  @var array
     */
    protected $__dict__ = array();

    /** Default to return Object<className>
    *   @return string
    */
    public function __toString()
    {
        $x = strtr(get_called_class(), '\\', '.');
        return $x;
    } // __toString

    /** return an array containing instance properties
    *   @return array
    */
    public function toArray()
    {
        return $this->__dict__;
    }


    /** isEmpty()
    *   @return bool
    */
    public function isEmpty()
    {
        return ! $this->__dict__;
    }


    /**
    *  Countable method
    *   @return int
    */
    public function count()
    {
        return count( $this->__dict__ );
    }


    /** extract the raw (native PHP-type) value from $value
     *  @param mixed $value
     *  @return null
     */
    public static function valueOf( $value )
    {
        return null;
    }


    /** Test if a value is usable as native-type and return it as native-type or throw exception
    *   @param mixed $value the value to test
    *   @return mixed php native type
    *   @throws TypeError
    */
    public static function test( $value )
    {
        $rvalue = static::valueOf( $value );
        if ( null === $rvalue )
            throw TypeError::_new_();
        return $rvalue;
    }


    /** get the class name of a class/object
     *   Works both with static class:: calls and with $instance-> calls
    *   @return string
     *  @final
     */
    final public static function _class_()
    {
        return get_called_class();
    }

    /** get the Unqualified class name of a class/object
     *   Works both with static class:: calls and with $instance-> calls
    *   @return string
     *  @final
     */
    final public static function _unclass_()
    {
        $c = get_called_class();
        $ns = strrpos($c, '\\');
        if (false !== $ns)
            $c = substr($c, $ns+1);
        return $c;
    }


    /** get the namespace name of a class/object
     *  Works both with static class:: calls and with $instance-> calls
     *  @return string : namespace of the object's class, no leading/trailing \
     *  @final
     */
    final public static function _ns_()
    {
        $c = get_called_class();
        $p = strrpos($c, '\\');
        if (false === $p)
            $ns = '';
        else
            $ns = substr($c, 0, $p);
        return $ns;
    }


    /**
     *  get the last part of the class name, equal to filename w/o extension
    *       eg. "Class" for Yz_Class, "Console" for Yz_Application_Console
    *   @return string
    *   @final
    */
    final public static function _name_()
    {
        return substr( strrchr( get_called_class(), '_' ), 1 );
    } // _name_


    /**
     *  Iterator methods
     */
    public function rewind()
    {
        reset($this->__dict__);
        return $this;
    }

    public function current()
    {
        return current($this->__dict__);
    }

    public function key()
    {
        return key($this->__dict__);
    }

    public function next()
    {
        next($this->__dict__);
        return $this;
    }

    public function valid()
    {
        return null !== key($this->__dict__) ;
    }


    /** call a getter for a property
    *   default to Exception when accessing an undefined variable or method
    *   @param scalar $var property name
    *   @return mixed
    *   @throw AttributeError
    *   @final
    */
    final public function __get( $var )
    {
        return $this->offsetGet($var);
    } // __get



    /** call a setter for a variable
    *       default to lookup
    *       throws when accessing an undefined variable or method
    *   @param scalar $var property name
    *   @param mixed $value
    *   @return chainable - ignored
    *   @throw AttributeError
    *   @final
    */
    final public function __set( $var, $value )
    {
        return $this->offsetSet($var, $value);
    } // set


    /**
    *  test if a property/key/index exists in the object
    *   @param scalar $var property name
    *   @return chainable - ignored
    *   @final
    */
    final public function __isset( $var )
    {
        return $this->offsetExists($var);
    } // __isset


    /**
    *  __unset()
    *   @param scalar $var
    *   @return chainable - ignored
    *   @throw AttributeError
    *   @final
    */
    final public function __unset( $var )
    {
        return $this->offsetUnset($var);
    } // __unset


    /** __call() : defaults to throw exception when an undefined method accessed
     *  @return null
    *   @throw NotImplementedError unconditionally
    */
    public function __call( $func, $args )
    {
        throw NotImplementedError::_new_();
    } // __call

    /** Generate exception on object invocation as function
     *  @return
     *  @throw NotImplementedError
     */
    public function __invoke()
    {
        throw NotImplementedError::_new_();
    } // __toString


    /** __callStatic() : defaults to throw exception when an undefined static method accessed
     *  @return null
    *   @throw MethodError unconditionally
    */
    public static function __callStatic( $method, $args )
    {
        throw MethodError::_new_(array('method' => $method));
    } // __callStatic


    /** __sleep() : defaults to throw exception
     *  @return null
    *   @throw NotImplementedError unconditionally
    */
    public function __sleep()
    {
        throw NotImplementedError::_new_();
    }

    /** __wakeup() : defaults to throw exception
     *  @return null
    *   @throw NotImplementedError unconditionally
    */
    public function __wakeup()
    {
        throw NotImplementedError::_new_();
    }

    /** __set_state() : defaults to throw exception
     *  @return null
    *   @throw NotImplementedError unconditionally
    */
    public static function __set_state( array $state )
    {
        throw NotImplementedError::_new_();
    }


    /*******************************
    ****   ArrayAccess methods   ***
    *******************************/

    /** test if a key/index/property exists in the object
     *      Verify the index and forward the operation to
     *      <code>_has_($var)</code>
     *  @param scalar $var
     *  @return bool
     *  @throw none
     */
    public function offsetExists( $var )
    {
        try {
            $var = $this->adjustIndex( $var );
            return $this->_has_( $var );
        } catch (TypeError $ex) {
            return false;
        } catch (AttributeError $ex) {
            return false;
        }
    }

    /** get a key/index/property
     *      Verify the index and forward the operation to
     *      <code>_get_($var)</code>
     *  @param scalar $var
     *  @return mixed
     *  @throw TypeError if bad index type
     *  @throw ValueError if index value not permissible
     *  @throw AttributeError if no such attribute
     */
    public function offsetGet( $var )
    {
        $var = $this->adjustIndex( $var );
        return $this->_get_( $var );
    }

    /** set a key/index/property
     *      Verify the index and forward the operation to
     *      <code>_set_($var, $value)</code>
     *  @param scalar $var
     *  @param mixed $value
     *  @return chainable
     *  @throw TypeError
     *  @throw AttributeError
     */
    public function offsetSet( $var, $value )
    {
        $var = $this->adjustIndex( $var );
        return $this->_set_( $var, $value );
    }

    /** `unset' a key/index/property
     *      Verify the index and forward the operation to
     *      <code>_del_($var, $value)</code>
     *  @param scalar $var
     *  @return chainable - mostly ignored
     *  @throw TypeError
     *  @throw AttributeError
     */
    public function offsetUnset( $var )
    {
        $var = $this->adjustIndex( $var );
        return $this->_del_( $var );
    }


    /***********************
     *   Protected methods
     **********************/


    /** validate the index, adjust it if required/possible
     *   @param mixed index
     *   @return mixed valid index
     *   @throw TypeError if index type not valid
     *   @throw AttributeError if index value not valid
     */
    protected function adjustIndex( $index )
    {
        $index = Yz_String::valueOf( $index );
        if (null === $index)
            throw TypeError::_new_();
        if ("" === $index)
            throw AttributeError::_new_(array('attr' => $index));
        return $index;
    }

    /** the default existence checker for properties: look up $__dict__
     *  @param string index
     *  @return bool
     */
    protected function _has_( $index )
    {
        return $this->__dict__ && array_key_exists($index, $this->__dict__);
    }

    /** the default getter for properties which don't have theirs own
     *  @param string index
     *  @return NEVER
     *  @throw AttributeError
     */
    protected function _get_( $index )
    {
        if (! $this->_has_( $index ))
            throw AttributeError::_new_(array('attr' => $index)); // NOTE index will be string-safe

        return $this->__dict__[$index];
    }

    /** the default setter for properties which don't have theirs own
     *  @param string index
     *  @param mixed value
     *  @return NEVER
     *  @throw AttributeError
     */
    protected function _set_( $index, $value )
    {
        if (! $this->_has_( $index ))
            throw AttributeError::_new_(array('attr' => $index)); // NOTE index will be string-safe

        $this->__dict__[$index] = $value;
        return $this;
    }

    /** the default unsetter for properties which don't have theirs own
     *  @param string index
     *  @return NEVER
     *  @throw AttributeError
     */
    protected function _del_( $index )
    {
        if (! $this->_has_( $index ))
            throw AttributeError::_new_(array('attr' => $index)); // NOTE index will be string-safe

        unset($this->__dict__[$index]);
        return $this;
    }

    public static function virtualClassContent()
    {
        return '';
    }

} // class

